/*
 * Copyright (C) 2010-2011 Graz University of Technology, Austria. All Rights reserved. 
 *
 * Contact: Simone Kopeinik <simone.kopeinik@tugraz.at>
 * 	   Graz University of Technology, Knowledge Management Institute, Brückenkopfgasse 1/6, 8020 Graz 	
 * 	   <http://www.kmi.tugraz.at/>
 * 
 * This software is part of the TARGET platform developed by the
 * TARGET EC-funded project www.reachyourtarget.org
 *
 * This software is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * The GNU General Public License is available at <http://www.gnu.org/licenses/>.
 *
 */
package at.tugraz.kmi.css.analyzer;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;

import org.apache.commons.collections15.Predicate;

import at.tugraz.kmi.css.analyzer.gapvis.GraphEdge;
import at.tugraz.kmi.css.analyzer.gapvis.JobProfile;
import at.tugraz.kmi.css.cbkst2.CbKSTCompetence;
import at.tugraz.kmi.css.cbkst2.PERSISTENCE_TYPE;
import at.tugraz.kmi.css.cbkst2.prereq.CompetenceManager;
import at.tugraz.kmi.css.commons.Bean;
import au.com.bytecode.opencsv.CSVReader;

import com.google.inject.Inject;
import com.google.inject.Singleton;

import edu.uci.ics.jung.algorithms.filters.Filter;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.ObservableGraph;
import edu.uci.ics.jung.graph.SparseGraph;
import edu.uci.ics.jung.graph.event.GraphEvent;
import edu.uci.ics.jung.graph.event.GraphEventListener;

/**
 * The {@linkplain CompetenceAnalyzer} is the conceptual model of the Competence
 * Analyzer system. The model describes the various entities involved in the
 * system and their relationships.
 * 
 * @author Georg Öttl
 * @author Simone Kopeinik
 * 
 */
@Singleton
public final class CompetenceAnalyzer extends Bean implements Serializable {

	private static final long							serialVersionUID					= 1L;

	/** Bean Property Name */
	public static final String							PROPERTY_TARGET_COMPETENCE_PROFILE	= "targetCompetenceProfile";

	/** Bean Property Name */
	public static final String							PROPERTY_CURRENT_COMPETENCE_PROFILE	= "currentCompetenceProfile";

	/** Bean Property Name */
	public static final String							PROPERTY_GAP_COMPETENCE_PROFILE		= "gapCompetenceProfile";

	/** Bean Property Name */
	public static final String							PROPERTY_JOB_PROFILES				= "jobProfiles";

	private ObservableGraph<UserCompetence, GraphEdge>	currentCompetenceProfile;
	private ObservableGraph<UserCompetence, GraphEdge>	targetCompetenceProfile;
	/** A list of job profiles. (See D4.1) */
	private Collection<JobProfile>						jobProfiles;

	private final CompetenceManager						competenceManager;
	private final transient Logger						log;

	@Inject
	CompetenceAnalyzer(CompetenceManager cbkstDM, Logger log) {
		this.log = log;
		// Just for initialisation purposes.
		setCurrentCompetenceProfile(new SparseGraph<UserCompetence, GraphEdge>());
		setTargetCompetenceProfile(new SparseGraph<UserCompetence, GraphEdge>());
		competenceManager = cbkstDM;
		CompetenceProfileReader reader = new CompetenceProfileReader();
		try {
			jobProfiles = reader.readData();
			InputStream resourceAsStream = getClass().getResourceAsStream(
                    "/hse.graphml");
			competenceManager.loadStructure(resourceAsStream,
					PERSISTENCE_TYPE.graphml);

		} catch (IOException e) {
			throw new RuntimeException(e);

		}

		Collection<CbKSTCompetence> c = new ArrayList<CbKSTCompetence>();
		for (JobProfile profile : jobProfiles) {
			Set<UserCompetence> s = profile.getCompetences();
			for (UserCompetence userCompetence : s) {
				c.add(userCompetence.getCompetence());
			}
		}
		competenceManager.addAll(c);
		log.info("Competence Analyzer initialisation finished");
	}

	/**
	 * Updates/Calculates the Competence Gap
	 */
	public void updateGapGraph() {
		firePropertyChange(PROPERTY_GAP_COMPETENCE_PROFILE, null,
				getGapCompetenceProfile());
	}

	/**
	 * Returns the Gap Competence Profile
	 * 
	 * @return the Gap Competence Profile as a Graph
	 */
	public Graph<UserCompetence, GraphEdge> getGapCompetenceProfile() {
		Filter<UserCompetence, GraphEdge> f = new GraphFilter(
				new Predicate<UserCompetence>() {
					@Override
					public boolean evaluate(UserCompetence vertex) {
						//boolean b = !currentCompetenceProfile.containsVertex(vertex);
					
						boolean b = ((!currentCompetenceProfile.containsVertex(vertex) && vertex.getProficiency()>0) || 
									(currentCompetenceProfile.containsVertex(vertex) && getProficiencyDifference(vertex)>0));
						return b;
					}

					private int getProficiencyDifference(UserCompetence vertex) {
						if (currentCompetenceProfile.containsVertex(vertex)){
							Iterator<UserCompetence> iter = currentCompetenceProfile.getVertices().iterator();
							while (iter.hasNext()){
								UserCompetence uc = (UserCompetence) iter.next();
								if (uc.equals(vertex)){
									return vertex.getProficiency() - uc.getProficiency(); 
								}
							}
					}
						return 0;
					}
				});
		return f.transform(targetCompetenceProfile);
	}

	/**
	 * Allows to set an arbitrary graph as the current gap
	 * 
	 * @param graph
	 */
	public void setGapCompetenceProfile(Graph<UserCompetence, GraphEdge> graph) {
		
		// Method only here for jgoodies compatibility. the gap profile shouldn't
		// be settable.
	}

	private static class GraphFilter implements
			Filter<UserCompetence, GraphEdge> {

		private final Predicate<UserCompetence>	vertexPred;

		public GraphFilter(Predicate<UserCompetence> vertex_pred) {
			vertexPred = vertex_pred;
		}

		@Override
		public Graph<UserCompetence, GraphEdge> transform(
				Graph<UserCompetence, GraphEdge> g) {

			Graph<UserCompetence, GraphEdge> filtered = new ObservableGraph<UserCompetence, GraphEdge>(
					new SparseGraph<UserCompetence, GraphEdge>());

			for (UserCompetence v : g.getVertices()) {
				if (vertexPred.evaluate(v)) {
					filtered.addVertex(v);
				}
			}

			Collection<UserCompetence> filtered_vertices = filtered
					.getVertices();

			for (GraphEdge e : g.getEdges()) {
				Collection<UserCompetence> incident = g.getIncidentVertices(e);
				if (filtered_vertices.containsAll(incident)) {
					filtered.addEdge(e, incident);
				}
			}

			return filtered;

		}
	}

	/**
	 * 
	 * Sets the current Competence Profile a User has.
	 * 
	 * @param currentCompetenceProfile
	 */
	public void setCurrentCompetenceProfile(
			Graph<UserCompetence, GraphEdge> currentCompetenceProfile) {
		Graph<UserCompetence, GraphEdge> old = this.currentCompetenceProfile;
		if (!(currentCompetenceProfile instanceof ObservableGraph)) {
			this.currentCompetenceProfile = new ObservableGraph<UserCompetence, GraphEdge>(
					currentCompetenceProfile);
		} else {
			this.currentCompetenceProfile = (ObservableGraph<UserCompetence, GraphEdge>) currentCompetenceProfile;
		}
		this.currentCompetenceProfile
				.addGraphEventListener(new GraphEventListener<UserCompetence, GraphEdge>() {
					@Override
					public void handleGraphEvent(
							GraphEvent<UserCompetence, GraphEdge> evt) {
						firePropertyChange(PROPERTY_CURRENT_COMPETENCE_PROFILE,
								null, evt.getSource());
						firePropertyChange(PROPERTY_GAP_COMPETENCE_PROFILE,
								null, getGapCompetenceProfile());
					}
				});
		firePropertyChange(PROPERTY_CURRENT_COMPETENCE_PROFILE, old,
				this.currentCompetenceProfile);
	}

	/**
	 * Returns the available JobProfiles of this Domain.
	 * 
	 * @return
	 */
	public Collection<JobProfile> getJobProfiles() {
		return jobProfiles;
	}

	/**
	 * Sets the available job profiles of this domain.
	 * 
	 */
	public void setJobProfiles(Collection<JobProfile> jobProfiles) {
		Collection<JobProfile> old = this.jobProfiles;
		this.jobProfiles = jobProfiles;
		firePropertyChange(PROPERTY_JOB_PROFILES, old, this.jobProfiles);
	}

	/**
	 * Returns the Current Competence Profile of the User.
	 */
	public Graph<UserCompetence, GraphEdge> getCurrentCompetenceProfile() {
		return currentCompetenceProfile;
	}

	/**
	 * Set the target competence profile of a user.
	 * 
	 * @param targetCompetenceProfile 
	 */
	public void setTargetCompetenceProfile(
			Graph<UserCompetence, GraphEdge> targetCompetenceProfile) {
		Graph<UserCompetence, GraphEdge> old = this.targetCompetenceProfile;
		if (!(targetCompetenceProfile instanceof ObservableGraph)) {
			this.targetCompetenceProfile = new ObservableGraph<UserCompetence, GraphEdge>(
					targetCompetenceProfile);
		} else {
			this.targetCompetenceProfile = (ObservableGraph<UserCompetence, GraphEdge>) targetCompetenceProfile;
		}
		this.targetCompetenceProfile
				.addGraphEventListener(new GraphEventListener<UserCompetence, GraphEdge>() {
					@Override
					public void handleGraphEvent(
							GraphEvent<UserCompetence, GraphEdge> evt) {
						firePropertyChange(PROPERTY_TARGET_COMPETENCE_PROFILE,
								null, evt.getSource());
						firePropertyChange(PROPERTY_GAP_COMPETENCE_PROFILE,
								null, getGapCompetenceProfile());
					}
				});

		firePropertyChange(PROPERTY_TARGET_COMPETENCE_PROFILE, old,
				this.targetCompetenceProfile);
	}

	/**
	 * returns the target competence profile of a user.
	 * 
	 * @return
	 */
	public Graph<UserCompetence, GraphEdge> getTargetCompetenceProfile() {
		return targetCompetenceProfile;
	}

}

class CompetenceProfileReader {

	private final HashMap<String, JobProfile>	profiles;

	public CompetenceProfileReader() {
		profiles = new HashMap<String, JobProfile>();
	}

	public Collection<JobProfile> readData() throws IOException {
		InputStream resourceAsStream = getClass().getResourceAsStream(
                "/TARGET-Competence-Profiles.csv");
		CSVReader reader = new CSVReader(
				new InputStreamReader(resourceAsStream), ',', '\"');
		List<String[]> lines = reader.readAll();

		for (String[] line : lines) {
			String competencesShort = line[0];
			String competencesLong = line[1];
			String competenceDescription = line[2];

			String role1 = line[3].trim();
			String role2 = line[4].trim();
			String role3 = line[5].trim();

			if (role1.length() != 0) {
				UserCompetence userCompetence = UserCompetence.of(
						competencesShort, competenceDescription);
				getProfile(role1).addCompetence(userCompetence);
				userCompetence.setCompetenceNameLong(competencesLong);
			}
			if (role2.length() != 0) {
				UserCompetence userCompetence = UserCompetence.of(
						competencesShort, competenceDescription);
				getProfile(role2).addCompetence(userCompetence);
				userCompetence.setCompetenceNameLong(competencesLong);
			}
			if (role3.length() != 0) {
				UserCompetence userCompetence = UserCompetence.of(
						competencesShort, competenceDescription);
				getProfile(role3).addCompetence(userCompetence);
				userCompetence.setCompetenceNameLong(competencesLong);
			}

		}

		return new HashSet<JobProfile>(profiles.values());
	}

	private JobProfile getProfile(String s) {
		if (profiles.containsKey(s)) {
			return profiles.get(s);
		} else {
			JobProfile value = new JobProfile(s);
			profiles.put(s, value);
			return value;
		}
	}

}
